using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
#if WINDOWS || XBOX
using Microsoft.Xna.Framework.Storage;
#endif
using System.Diagnostics;
using The_LarX.Rendering;
//using SynapseGaming.LightingSystem.Rendering;

namespace The_LarX
{
    /// <summary>
    /// Class that Manages every single task of the LarX ---
    /// Do not access any member of this static class without before
    /// calling Load(...), this may cause Exceptions. Use CreateDefaultManagers(...)
    /// for the Engine to make the Default Managers, or load your own Managers using 
    /// UseCustomManagers(...)
    /// </summary>
    public static class Engine
    {
        #region Events
        /// <summary>
        /// Occurs when the Engine's Reset method is called
        /// </summary>
        public static event EventHandler OnReset;

        /// <summary>
        /// Occurs when the Load(...) method is finished running
        /// </summary>
        public static event EventHandler OnFinishLoading;

        #endregion

        /// <summary>
        /// Exits the Game.
        /// </summary>
        public static void Exit()
        {
            Engine.Game.Exit();
        }

        #region Variables

        #region Managers

        /// <summary>
        /// Manages all TemporizedActions that you can create in the Engine.
        /// </summary>
        public static TemporizedActionManager TemporizedActions;

        /// <summary>
        /// Class that handles a Method call Queue
        /// </summary>
        public static QueuedActionManager QueuedActions;

        /// <summary>
        /// The Engine's ContentManager, modified. It can load
        /// LarX files without using Custom Processors. Use 
        /// Engine.Content.LoadLar(AssetPlace) to load a Lar asset.
        /// Acceptable Types:
        ///  - LarImage
        ///  - Scene(returns the Editor GameScreen)
        ///  Don't forget to use the Extension when using Lar Files
        /// </summary>
        public static LarCustomContentManager Content;

        /// <summary>
        /// FontManager, every font loaded goes to this manager. --- 
        /// You can easily inserting Fonts in your game by putting a Font file
        /// in the Engine\\Fonts folder. This class will load the SpriteFont files
        /// automatically, and give them a name(the file name). In Windows Phone 7,
        /// you need to make a file called Fonts.Fonts, with all the fonts, with the LarX
        /// Editor
        /// </summary>
        public static FontManager FontManager;

        /// <summary>
        /// Render System. Contains all Methods and ways
        /// to Render Sprites, Strings and Models
        /// </summary>
        public static RenderManager RenderManager;

        /// <summary>
        /// The InputManager, handles input for Xbox 360 GamePads and PC Keyboard.
        /// ALL Gestures from WP7 Screen are saved in this class.
        /// (WIP) On PC, Custom GamePads are being inserted
        /// </summary>
        public static InputManager Input;

        /// <summary>
        /// Global Variables --- Editor Stuff
        /// </summary>
        public static GlobalsManager Globals;

        /// <summary>
        /// Used for saving. Manages the Current Storage Device. 
        /// Selects the StorageDevice quickly and easily.
        /// </summary>
        public static StorageDeviceManager StorageDeviceManager;

        /// <summary>
        /// Command Window shows off Some useful Commands if you're on an Xbox 360
        /// </summary>
        public static LarCommandWindow CommandWindow;

        /// <summary>
        /// Class that manages all Console Messages
        /// </summary>
        public static LarConsole Console;

        #endregion

        #region Else
        /// <summary>
        /// If you are using Any Networking... Set this, or
        /// opening any Guide Like Components will drop the Network
        /// </summary>
        public static bool UsingNetworking = false;

        /// <summary>
        /// Collection of GameScreens of the Engine
        /// </summary>
        public static GameScreensList GameScreens;        

        /// <summary>
        /// If the Engine Has Focus. If false it won't Update(this is checked on the Update method)
        /// </summary>
        public static bool HasFocus = true;

        /// <summary>
        /// The current FPS
        /// </summary>
        public static float FPS;

        /// <summary>
        /// If the LarX Guide is Visible
        /// </summary>
        public static bool LarXGuideVisible
        {
            get
            {
                if (UsingNetworking)
                    return false;
                else
                    if (GuideLikeComponent.ActiveComponents.Count > 0)
                    {
                        for (int i = 0; i < GuideLikeComponent.ActiveComponents.Count; i++)
                        {
                            if (GuideLikeComponent.ActiveComponents[i].Visible &&
                                GuideLikeComponent.ActiveComponents[i].NeedToBlockUpdate)
                                return true;
                        }
                        return false;
                    }
                    else
                        return false;
            }
        }

        /// <summary>
        /// Engine's Graphics Device
        /// </summary>
        public static GraphicsDevice GraphicsDevice;

        /// <summary>
        /// The Graphics Device Manager. This Manager is not created by the Engine.
        /// </summary>
        public static GraphicsDeviceManager GraphicsDeviceManager;

        /// <summary>
        /// This is passed through the Engine's Setup Method. It can be a 
        /// GraphicsDeviceManager, or other classes, so it's stored here
        /// </summary>
        public static IGraphicsDeviceService IGraphicsDeviceService;

        /// <summary>
        /// If the Engine has been Initialized
        /// </summary>
        public static bool Initialized
        {
            get { return initialized; }
        }
        private static bool initialized = false;

        /// <summary>
        /// Engine's GameTime
        /// </summary>
        public static GameTime GameTime
        {
            get { return gameTime; }
        }
        private static GameTime gameTime;        
        
        /// <summary>
        /// ServiveContainer, for handling services and the
        /// ContentManager
        /// </summary>
        public static ServiceContainer Services { get; private set; }       

        /// <summary>
        /// The main Game class. Useful, for functions like
        /// Exit(). Useful for handling XNA Game Components, like
        /// the GamerServices
        /// </summary>
        public static Game Game;

        /// <summary>
        /// If the Engine is Paused. ---
        /// If so, it doesn't update/draw. ---
        /// </summary>
        public static bool Paused = false;                        

        /// <summary>
        /// How much is the milliseconds target per second the game is calling Update/Draw.
        /// </summary>
        public static float MillisecondsPerSecond;

        /// <summary>
        /// The Engine's SpeedFactor. If the GameSpeed is normal, it will return 1, 
        /// if it isn't, will return a value that you can multiply to make smooth
        /// movement. It's a simple division of the GameTime elapsed Milliseconds / Ticks per Second.
        /// </summary>
        public static float SpeedFactor
        {
            get { return Engine.GameTime.ElapsedGameTime.Milliseconds / MillisecondsPerSecond; }
        }

        /// <summary>
        /// If the Engine is currently on PlayMode. If setted to True, it will call Every Component's
        /// Method SetToInitialState(),
        /// </summary>
        public static bool PlayMode
        {
            get { return playing; }
            set
            {
                playing = value;
                if (!value)
                {
                    if (Initialized)
                    {
                        for (int j = 0; j < GameScreens.Count; j++)
                        {
                            for (int i = 0; i < GameScreens[j].Components.Count; i++)
                            {
                                GameScreens[j].Components[i].SetToInitialState();
                            }
                        }
                    }
                }
                else
                {
                    if (Initialized)
                    {
                        for (int j = 0; j < GameScreens.Count; j++)
                        {
                            for (int i = 0; i < GameScreens[j].Components.Count; i++)
                            {
                                GameScreens[j].Components[i].SetToPlayMode();
                            }
                        }                        
                    }
                }
            }
        }
        private static bool playing;        

        /// <summary>
        /// The Ration, Screen Width / Screen Height, it's used for 3D
        /// rendering
        /// </summary>
        public static float AspectRatio
        {
            get { return aspectRatio; }
        }
        private static float aspectRatio;

        /// <summary>
        /// If we are working in Editor mode
        /// </summary>
        public static bool Editor = false;

        /// <summary>
        /// An FPS Counter, can be enabled by pressing the F2 Key
        /// </summary>
        public static FPSCounter Counter;        

        private static bool testMode;
        public static bool DebugMode;
        /// <summary>
        /// If this is set to True, every Test command will be available. 
        /// ON WINDOWS: They are:
        /// F2  - FPS Counter
        /// F3  - Open Console
        /// F4  - Debug Mode
        /// F11 - Editor
        /// ON XBOX 360: You have to open the LarX Command Window, by holding
        /// BACK and pressing START at the same time. You can add Options to the LarX 
        /// Command System by changing it's variables using Engine.CommandWindow class
        /// </summary>
        public static bool TestMode
        {
            get { return testMode; }
            set 
            { 
                testMode = value;
                if (Counter == null)
                {
                    Counter = new FPSCounter();
                    Counter.Visible = false;
                    Counter.Sprite.Position = Vector2.Zero;
                }
                if (CommandWindow == null)
                {
                    CommandWindow = new LarCommandWindow();
                    CommandWindow.Visible = false;
                }
                if (Console == null)
                {
                    Console = new LarConsole();
                    Console.Visible = false;
                }
            }
        }

        #endregion

        #endregion

        #region Load


        /// <summary>
        /// Creates all the Engine DefaultManagers
        /// </summary>
        public static void CreateDefaultManagers(string GameFolder)
        {
            ///TemporizedActions, QueuedActions, PerfomanceHelper,
            ///Content, FontManager, RenderManager, Input,
            ///Globals, StorageDeviceManager, CommandWindow, LarConsole
            Engine.Content = new LarCustomContentManager(Services);
            Engine.Content.RootDirectory = "Content";

            Engine.TemporizedActions = new TemporizedActionManager();
            Engine.QueuedActions = new QueuedActionManager();

            Engine.Input = new InputManager();

            Engine.StorageDeviceManager = new StorageDeviceManager();
            Engine.Globals = new GlobalsManager();

            Engine.RenderManager = new RenderManager(GraphicsDevice);
            Engine.RenderManager.Load();
            Engine.RenderManager.BackgroundColor = Color.Black;

            Engine.FontManager = new FontManager();

            if (GameFolder == string.Empty)
            {
                LanguageManager.Load(string.Empty);
            }
            else
            {
                LanguageManager.Load(PathUtil.MakeEngineable(Path.Combine(GameFolder, "Languages")));
            }
        }        
        /// <summary>
        /// Loads the Engine, preparing everything for duty!
        /// </summary>
        /// <param name="Game">Your main Game class, so the Engine can Exit the game.</param>
        /// <param name="GraphicsDeviceService">Needed for access to the GraphicsDevice, and everything with the Graphics Card, like setting to Fullscreen or changing the Resolution.</param>
        /// <param name="GameFolder">The folder your game Content is. Used for automatics systems</param>
        /// <param name="TicksPerSecond">The Target Ticks per Second your game has. Used to calculate the SpeedFactor</param>
        public static void Load(float TicksPerSecond, IGraphicsDeviceService GraphicsDeviceService,
            Game Game)
        {
            Engine.MillisecondsPerSecond = TicksPerSecond;

            Engine.IGraphicsDeviceService = GraphicsDeviceService;
            
            Engine.GameScreens = new GameScreensList();

            Engine.Services = new ServiceContainer();
            Engine.Services.AddService(typeof(IGraphicsDeviceService), GraphicsDeviceService);

            Engine.GraphicsDevice = GraphicsDeviceService.GraphicsDevice;            

            if (GraphicsDeviceService is GraphicsDeviceManager)
            {
                Engine.GraphicsDeviceManager = GraphicsDeviceService as GraphicsDeviceManager;
            }
            
            Engine.Game = Game;                 

            Engine.aspectRatio = Engine.GraphicsDevice.Viewport.AspectRatio;

            Engine.gameTime = new GameTime();

            Engine.initialized = true;

            if (OnFinishLoading != null)
                OnFinishLoading(null, EventArgs.Empty);
        }

        
        #endregion

        #region Update

        public static void Update(GameTime GameTime)
        {
            long startUpdateTime = Stopwatch.GetTimestamp();
            Engine.gameTime = GameTime;
            FPS = 1 / (float)GameTime.ElapsedGameTime.TotalSeconds;

            Input.Update();

#if WINDOWS
            if (Input.MouseState.X > Engine.GraphicsDevice.Viewport.Width ||
                Input.MouseState.Y > Engine.GraphicsDevice.Viewport.Height ||
                Input.MouseState.X < 0 ||
                Input.MouseState.Y < 0)
            {
                Engine.HasFocus = false;
            }
            else
            {
                Engine.HasFocus = true;
            }
#endif

            if (Engine.HasFocus || UsingNetworking)
            {
                if (!Guide.IsVisible || UsingNetworking)
                {
                    if (testMode)
                    {
                        if (Counter.Visible)
                            Counter.Update();

                        if (Engine.Input.IsKeyboardPress(Keys.F2))
                        {
                            /// FPS Counter
                            if (Counter.Visible)
                            {
                                if (Counter.FPSType == FPSType.Number)
                                {
                                    Counter.FPSType = FPSType.Graphic;
                                }
                                else
                                {
                                    Counter.Visible = !Counter.Visible;
                                }
                            }
                            else
                            {
                                Counter.FPSType = FPSType.Number;
                                Counter.Visible = !Counter.Visible;
                            }
                        }
                        if (Engine.Input.IsKeyboardPress(Keys.F3))
                        {
                            /// Console
                            Console.Visible = !Console.Visible;
                        }
                        if (Engine.Input.IsKeyboardPress(Keys.F4))
                        {
                            /// Debug Mode
                            DebugMode = !DebugMode;
                        }
                        if (Engine.Input.IsKeyboardPress(Keys.F11))
                        {
                            /// LarX Custom Editor
                        }

                        /// XBOX 360
                        if (Engine.Input.IsActivePlayerButtonPress(Buttons.Start) &&
                            Engine.Input.IsActivePlayerButtonDown(Buttons.Back))
                        {
                            /// Command Window
                            CommandWindow.Visible = !CommandWindow.Visible;
                        }
                    }
                    if (Engine.Input.IsKeyboardDown(Keys.LeftAlt) &&
                        Engine.Input.IsKeyboardPress(Keys.Enter))
                    {
                        if (GraphicsDeviceManager != null)
                        {
                            GraphicsDeviceManager.IsFullScreen = !GraphicsDeviceManager.IsFullScreen;
                            GraphicsDeviceManager.ApplyChanges();
                        }
                    }
                    if (!LarXGuideVisible)
                    {
                        if (!Paused)
                        {
                            StorageDeviceManager.Update();

                            if (!StorageDeviceManager.InActivity)
                            {
                                for (int i = 0; i < GameScreens.Count; i++)
                                {
                                    if (GameScreens[i].Visible)
                                        GameScreens[i].Update();
                                }
                                TemporizedActions.Update();
                            }

                            if (Engine.Input.PlayingPlayerStartChange)
                            {
                                if (Engine.Input.IsAnyPlayerButtonDown(Buttons.Start))
                                {
                                    if (Engine.Input.PlayingPlayer != Engine.Input.lastPressedPlayer)
                                    {
                                        Engine.Input.PlayingPlayer = Engine.Input.lastPressedPlayer;
                                        DownMessage msg = new DownMessage(LanguageManager.GetEngineString(9) + ((int)Engine.Input.PlayingPlayer + 1).ToString(), 1500);
                                        if (gPadMessage != null)
                                        {
                                            gPadMessage.Disable();
                                            gPadMessage = null;
                                        }
                                        //LarMessageBox MessageBox = new LarMessageBox("Player GamePad Changed");
                                    }
                                }
                            }
                        }
                    }
                    UpdateGuideLikeComponents();
                }
            }

            long endUpdateTime = Stopwatch.GetTimestamp();
            double accumulatedPhysicsTime = (endUpdateTime - startUpdateTime) / (double)Stopwatch.Frequency;
            UpdateTime = (int)(accumulatedPhysicsTime * 1000);
        }
        private static int UpdateTime;

        private static void UpdateGuideLikeComponents()
        {
            // Execute GuideLike Components after everything was Update
            for (int i = 0; i < GuideLikeComponent.ActiveComponents.Count; i++)
                if (GuideLikeComponent.ActiveComponents[i].Visible)
                    GuideLikeComponent.ActiveComponents[i].Update();
        }
        #endregion

        #region Draw

        public static void Draw(GameTime GameTime)
        {
            long startDrawTime = Stopwatch.GetTimestamp();

            Engine.gameTime = GameTime;
            FPS = 1 / (float)GameTime.ElapsedGameTime.TotalSeconds;
            if (!Paused)
            {
                for (int i = 0; i <= QueuedActions.BiggestStartQueued; i++)
                {
                    if (QueuedActions.StartDrawQueuedAction.ContainsKey(i))
                    {
                        QueuedActions.StartDrawQueuedAction[i].Invoke();
                        QueuedActions.StartDrawQueuedAction.Remove(i);
                    }
                }
                QueuedActions.BiggestStartQueued = 0;

                RenderManager.Clear();

                double gs = 0;
                long GameScreenStart = 0;
                for (int i = 0; i < GameScreens.Count; i++)
                {
                    if (i == 0)
                    {
                        GameScreenStart = Stopwatch.GetTimestamp();
                    }
                        if (GameScreens[i].Visible)
                            GameScreens[i].Draw();
                    if (i == 0)
                    {
                        long GameScreenEnd = Stopwatch.GetTimestamp();
                        gs = (GameScreenEnd - GameScreenStart) / (double)Stopwatch.Frequency;
                    }
                }

                if (Engine.DebugMode)
                {
                    int gsTime = (int)(gs * 1000);
                    Engine.RenderManager.DrawString("GameScreen(0)" + gsTime.ToString(), 2, 200,
                        new Vector2(50, 250), "Kootenay", Color.White, null);
                }

                for (int i = 0; i <= QueuedActions.BiggestQueued; i++)
                {
                    if (QueuedActions.DrawQueuedActions.ContainsKey(i))
                    {
                        QueuedActions.DrawQueuedActions[i].Invoke();
                        QueuedActions.DrawQueuedActions.Remove(i);
                    }
                }
                QueuedActions.BiggestQueued = 0;
            }

            // Execute GuideLike Components after everything was drawed
            for (int i = 0; i < GuideLikeComponent.ActiveComponents.Count; i++)
                if (GuideLikeComponent.ActiveComponents[i].Visible)
                    GuideLikeComponent.ActiveComponents[i].Draw();

            if (testMode)
            {
                if (Counter.Visible)
                    Counter.Draw();
                if (DebugMode)
                {
                    for (int i = 0; i < GameScreens.Count; i++)
                    {
                        GameScreens[i].DrawOnDebugMode();
                    }
                }
            }
            int playerBut = Input.IsNonActivePlayerAnyButtonDown();
            if (playerBut != -1)
            {
                if (gPadMessage == null)
                {
                    gPadMessage = new DownMessage(LanguageManager.GetEngineString(8) + (playerBut + 1), 1500);
                    gPadMessage.Finished += new EventHandler(gPadMessage_Finished);
                }
                else
                {
                    gPadMessage.RestartTimer();
                }
            }
            if (Engine.DebugMode)
            {
                long endDrawTime = Stopwatch.GetTimestamp();
                double accumulatedPhysicsTime = (endDrawTime - startDrawTime) / (double)Stopwatch.Frequency;
                int DrawTime = (int)(accumulatedPhysicsTime * 1000);

                Engine.RenderManager.DrawString("UpdateTime:" + UpdateTime.ToString(), 2, 200,
                    new Vector2(50, 100), "Kootenay", Color.White, null);
                Engine.RenderManager.DrawString("DrawTime:" + DrawTime.ToString(), 2, 200,
                    new Vector2(50, 150), "Kootenay", Color.White, null);

                Engine.RenderManager.DrawString("Memmory:" + (GC.GetTotalMemory(false) / 1024), 2, 200, new Vector2(50, 300),
                    "Kootenay", Color.White, null);
            }
        }

        private static DownMessage gPadMessage;
        private static void gPadMessage_Finished(object sender, EventArgs e)
        {
            gPadMessage = null;   
        }
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Reset the Engine, cleaning EVERYTHING.
        /// * ContentManager unload every single Asset
        /// * Every GameScreen is disabled
        /// * The Services are Cleared
        /// * The Fonts are loaded again
        /// </summary>
        /// <param name="KeepGlobals">If it should keep the Globals, or delete them</param>
        public static void Reset(bool KeepGlobals)
        {
            int NumberOfCantDisable = 0;
            while (GameScreens.Count != NumberOfCantDisable)
            {
                NumberOfCantDisable = 0;
                for (int i = 0; i < GameScreens.Count; i++)
                {
                    if (GameScreens[i].CanDisable)
                    {
                        GameScreens[i].Disable();
                    }
                    else
                    {
                        NumberOfCantDisable++;
                    }
                }
            }

            Engine.Services.Clear();                       
            Engine.Content.Unload();

            Engine.FontManager.Load();
            Engine.RenderManager.Load();

            Engine.Content = new LarCustomContentManager(Services);
            Engine.Content.RootDirectory = "Content";

            if (!KeepGlobals)
            {
                Engine.Globals.Floats.Clear();
                Engine.Globals.Ints.Clear();
                Engine.Globals.Strings.Clear();
                Engine.Globals.Bools.Clear();
            }
                        
            EngineSystem.Actors.Clear();
            EngineSystem.BepuEntities.Clear();
            EngineSystem.Cameras.Clear();
            EngineSystem.Components.Clear();
            EngineSystem.LightsInterface.Clear();
            EngineSystem.Renderers.Clear();
            EngineSystem.RenderersList.Clear();            
            EngineSystem.Sprites.Clear();
            EngineSystem.Trees.Clear();
            EngineSystem.Triggers.Clear();
            EngineSystem.Viewports.Clear();
            EngineSystem.VisualScripts.Clear();            

            Actor.ResetPickColor();

            if (OnReset != null)
                OnReset(null, EventArgs.Empty);
        }

        #endregion            
    }
}
